#ifndef TCP_CONNECTION_H
#define TCP_CONNECTION_H

#include <memory>

#include <inttypes.h>

#ifdef NATIVE_SOCKET
#include <unistd.h>
#include <sys/uio.h>
#include <errno.h>
#else
#include "tcp_impl.h"
#endif
#include "endpoint_addr.h"


namespace usnet {
class TCPConnection {
public:
    ~TCPConnection();

#ifndef NATIVE_SOCKET
    static std::unique_ptr<TCPConnection>
    MakeUnique(EndpointAddr* laddr, const EndpointAddr& raddr, const int64_t isn = -1);
#else
    static std::unique_ptr<TCPConnection>
    MakeUnique(EndpointAddr* laddr, const EndpointAddr& raddr, const int& connfd);
#endif

    inline int GetConnFD() const
    {
        return fd_;
    }

#ifndef NATIVE_SOCKET
    inline int Send(const char* data, const uint32_t len)
    {
        if (data == nullptr) {
            return -1;
        }
        if (len == 0) {
            return -1;
        }
        if (tcp_impl_ == nullptr) {
            return -1;
        }
        return tcp_impl_->SendData(PSH_BIT | ACK_BIT, data, len);
    }
        
    inline int Recv(char* buf, const uint32_t len)
    {
        if (buf == nullptr) {
            return -1;
        }
        if (len == 0) {
            return -1;
        }
        if (tcp_impl_ == nullptr) {
            return -1;
        }
        return tcp_impl_->RecvData(buf, len);
    }

    inline int Send(const struct iovec* iov, const uint32_t len)
    {
        if (iov == nullptr) {
            return -1;
        }
        if (len == 0) {
            return -1;
        }
        if (tcp_impl_ == nullptr) {
            return -1;
        }
        return tcp_impl_->SendData(PSH_BIT | ACK_BIT, iov, len);
    }
        
    inline int Recv(struct iovec* iov, const uint32_t len)
    {
        if (iov == nullptr) {
            return -1;
        }
        if (len == 0) {
            return -1;
        }
        if (tcp_impl_ == nullptr) {
            return -1;
        }
        return tcp_impl_->RecvData(iov, len);
    }
#else
    inline int Send(const char* data, const uint32_t len)
    {
        if (data == nullptr) {
            return -1;
        }
        if (len == 0) {
            return -1;
        }
        int ret = write(fd_, data, len);
        if ((ret < 0) and (errno == EAGAIN or errno == EINTR)) {
            return 0;
        }
        return ret;
    }
        
    inline int Recv(char* buf, const uint32_t len)
    {
        if (buf == nullptr) {
            return -1;
        }
        if (len == 0) {
            return -1;
        }
        int ret = read(fd_, buf, len);
        if ((ret < 0) and (errno == EAGAIN or errno == EINTR)) {
            return 0;
        }
        if (ret == 0) {
            return -1;
        }
        return ret;
    }

    inline int Send(const struct iovec* iov, const uint32_t len)
    {
        if (iov == nullptr) {
            return -1;
        }
        if (len == 0) {
            return -1;
        }
        int ret = writev(fd_, iov, len);
        if ((ret < 0) and (errno == EAGAIN or errno == EINTR)) {
            return 0;
        }
        if (ret == 0) {
            return -1;
        }
        return ret;
    }
        
    inline int Recv(struct iovec* iov, const uint32_t len)
    {
        if (iov == nullptr) {
            return -1;
        }
        if (len == 0) {
            return -1;
        }
        int ret = readv(fd_, iov, len);
        if ((ret < 0) and (errno == EAGAIN or errno == EINTR)) {
            return 0;
        }
        if (ret == 0) {
            return -1;
        }
        return ret;
    }
#endif

#ifndef NATIVE_SOCKET
    inline uint32_t Recv(const struct iovec** iov, const uint32_t size)
    {
        if (iov == nullptr) {
            return 0;
        }
        if (size == 0) {
            return 0;
        }
        if (tcp_impl_ == nullptr) {
            return 0;
        }
        return tcp_impl_->RecvData(iov, size);
    }

    inline uint32_t Reserve(const struct iovec** iov, const uint32_t size)
    {
        if (iov == nullptr) {
            return 0;
        }
        if (size == 0) {
            return 0;
        }
        if (tcp_impl_ == nullptr) {
            return 0;
        }
        return tcp_impl_->Reserve(iov, size);
    }

    inline void Discard(const struct iovec* iov)
    {
        if (iov == nullptr) {
            return;
        }
        if (tcp_impl_ ==  nullptr) {
            return;
        }
        return tcp_impl_->Discard(iov);
    }

    inline bool Commit(const struct iovec* iov)
    {
        if (iov == nullptr) {
            return false;
        }
        if (tcp_impl_ == nullptr) {
            return false;
        }
        return tcp_impl_->Commit(iov);
    }
#endif

private:
#ifndef NATIVE_SOCKET
    TCPConnection(EndpointAddr* laddr, const EndpointAddr& raddr, const int64_t isn = -1):
        isn_   { isn },
        laddr_ { laddr },
        raddr_ { raddr }
    {
        // nothing to do
    }
                
    inline bool InitTCPImpl()
    {
        tcp_impl_ = TCPImpl::MakeUnique(laddr_, &raddr_, isn_);
        if (tcp_impl_ == nullptr) {
            return false;
        }
        return true;
    }
#else
    TCPConnection(EndpointAddr* laddr, const EndpointAddr& raddr, const int& connfd):
        fd_ { connfd },
        laddr_ { laddr },
        raddr_ { raddr }
    {
        // nothing to do
    }
#endif

    int fd_ { -1 };
    EndpointAddr* laddr_ { nullptr };
    EndpointAddr  raddr_;
#ifndef NATIVE_SOCKET
    const int64_t isn_ { -1 };
    std::unique_ptr<TCPImpl> tcp_impl_ { nullptr };
#endif
    };
} // end of namespace usnet
#endif